﻿using BeetleX.Dispatchs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

#nullable disable
namespace BeetleX.XRPC.Awaiter
{
    public class AwaiterFactory
    {
        private Timer mTimer;
        private int mID;
        private int mStartID;
        private int mEndID;
        private DispatchCenter<AwaiterItem> mTimeDispatch;
        private AwaiterFactory.AwaiterGroup mAwaiterItemGroup = new AwaiterFactory.AwaiterGroup();

        public AwaiterFactory(bool isServer)
        {
            if (isServer)
            {
                this.mID = 1;
                this.mStartID = 1;
            }
            else
            {
                this.mID = 2;
                this.mStartID = 2;
            }
            this.mEndID = 99999999;
            this.mTimer = new Timer(new TimerCallback(this.OnTimeout), (object)null, 1000, 1000);
            this.mTimeDispatch = new DispatchCenter<AwaiterItem>(new Action<AwaiterItem>(this.OnTimeProcess));
        }

        private void OnTimeProcess(AwaiterItem item)
        {
            this.Completed(item, new RPCPacket()
            {
                Status = 408,
                Data = new object[1] { ("Request " + item.Request.Url + " time out!") }
            });
        }

        private void OnTimeout(object state)
        {
            try
            {
                this.mTimer.Change(-1, -1);
                IList<AwaiterItem> timeouts = this.mAwaiterItemGroup.GetTimeouts((double)TimeWatch.GetElapsedMilliseconds());
                if (timeouts.Count <= 0)
                    return;
                for (int index = 0; index < timeouts.Count; ++index)
                    this.mTimeDispatch.Enqueue(timeouts[index]);
            }
            catch
            {
            }
            finally
            {
                this.mTimer.Change(1000, 1000);
            }
        }

        internal AwaiterItem GetItem(int id) => this.mAwaiterItemGroup.Get(id);

        public (int, TaskCompletionSource<RPCPacket>) Create(
          RPCPacket request,
          Type[] resultType,
          int timeout = 10000)
        {
            request.NeedReply = true;
            int num = 0;
            lock (this)
            {
                this.mID += 2;
                if (this.mID >= this.mEndID)
                    this.mID = this.mStartID;
                num = this.mID;
            }
            long expiredTime = TimeWatch.GetElapsedMilliseconds() + (long)timeout;
            AwaiterItem awaiterItem = new AwaiterItem();
            awaiterItem.ID = num;
            awaiterItem.ResultType = resultType;
            awaiterItem.Request = request;
            this.mAwaiterItemGroup.Set(awaiterItem.ID, awaiterItem);
            return (num, awaiterItem.Create(expiredTime));
        }

        public bool Completed(AwaiterItem item, RPCPacket data)
        {
            if (!item.Completed(data))
                return false;
            item.Response = (RPCPacket)null;
            item.Request = (RPCPacket)null;
            return true;
        }

        public class AwaiterGroup
        {
            private const int Groups = 10;
            private List<AwaiterFactory.AwaiterGroup.GroupItem> mGroups = new List<AwaiterFactory.AwaiterGroup.GroupItem>();

            public AwaiterGroup()
            {
                for (int index = 0; index < 10; ++index)
                    this.mGroups.Add(new AwaiterFactory.AwaiterGroup.GroupItem());
            }

            public void Set(int id, AwaiterItem item) => this.mGroups[id % 10].Set(id, item);

            public AwaiterItem Get(int id) => this.mGroups[id % 10].Get(id);

            public IList<AwaiterItem> GetTimeouts(double time)
            {
                List<AwaiterItem> items = new List<AwaiterItem>();
                for (int index = 0; index < 10; ++index)
                    this.mGroups[index].GetTimeouts(items, time);
                return (IList<AwaiterItem>)items;
            }

            public class GroupItem
            {
                private ConcurrentDictionary<int, AwaiterItem> mItems = new ConcurrentDictionary<int, AwaiterItem>();

                public void Set(int id, AwaiterItem item) => this.mItems[id] = item;

                public AwaiterItem Get(int id)
                {
                    AwaiterItem awaiterItem;
                    this.mItems.TryRemove(id, out awaiterItem);
                    return awaiterItem;
                }

                public void GetTimeouts(List<AwaiterItem> items, double time)
                {
                    if (this.mItems.Count <= 0)
                        return;
                    foreach (AwaiterItem awaiterItem in (IEnumerable<AwaiterItem>)this.mItems.Values)
                    {
                        if (time > awaiterItem.TimeOut)
                            items.Add(this.Get(awaiterItem.ID));
                    }
                }
            }
        }
    }
}
